package czk.concrent.enjoyModelWithNotLock.stamplock;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.StampedLock;

/**
 * StampedLock中引入了一个stamp（邮戳）的概念。它代表线程获取到锁的版本，每一把锁都有一个唯一的stamp。是一个long类型的数字。
 *
 * @Author:ChenZhangKun
 * @Date: 2022/9/19 16:09
 */
public class StampedLockDemo {
    public static void main(String[] args) {
        StampedLock lock = new StampedLock();
        Balance balance = new Balance(10000L);
        Runnable w = () -> {
            // writeLock，是排它锁、不可重入锁、也叫独占锁，相同时间只能有一个线程获取锁，其他线程请求读锁和写锁都会被阻塞，当前没有线程持有读锁或写锁的时候才可以获得获取到该锁。
            // tryWriteLock，和writeLock类似，唯一的区别就是它非阻塞的特性，当获取不到锁时不会阻塞线程但是会返回一个stamp = 0的标识。
            // stamp > 0表示成功获取到锁；stamp = 0表示未获取到锁，但不会阻塞线程
            long writeLock = lock.writeLock();
            // 累加
            balance.setAccount(balance.getAccount() + 1000L);
            System.out.println("Write: " + balance.getAccount());
            lock.unlock(writeLock);
        };

        Runnable r = () -> {
            // 乐观锁认为数据不会轻易的被修改，因此在操作数据前并没有加锁（使用CAS方式更新锁的状态），而是采用试探的方式。只要当前没有写锁就可以获得一个非0的stamp，如果已经存在写锁则返回一个为0的stamp。
            // 由于没有使用CAS方法，也没有真正的加锁，所以并发性能要比readLock还要高。但是由于没有使用真正的锁，如果数据中途被修改，就会造成数据不一致问题。
            // 特别适用于读多写少的高并发场景。
            long l = lock.tryOptimisticRead();
            if (!lock.validate(l)) {
                // 不通过，获取读锁
                long readLock = lock.readLock();
                try {
                    System.out.println("read: " + balance.getAccount());
                } finally {
                    lock.unlock(readLock);
                }
            } else {
                System.out.println("Optimistic read fails");
            }
        };
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 50; i++) {
            executorService.submit(w);
            executorService.submit(r);
        }
    }

    private static class Balance {
        private Long account;

        public Balance(Long account) {
            this.account = account;
        }

        public Long getAccount() {
            return account;
        }

        public void setAccount(Long account) {
            this.account = account;
        }
    }
}
